package com.zg.assistant.socket;

import com.zg.assistant.service.CommunicationService;
import com.zg.common.core.C;
import com.zg.common.core.User;
import com.zg.common.core.constant.AssistantContants;
import com.zg.common.core.util.RedisUtil;
import com.zg.common.core.util.TokenUtil;
import com.zg.common.web.socket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * 扩展的WebSocket服务器
 * 添加了消息处理功能和用户认证
 *
 * @author Claude
 */
@ServerEndpoint("/zgws/{clientId}")
@Component
@Slf4j
public class ZgWebSocketServer extends WebSocketServer {

    /**
     * 使用静态变量注入RedisUtil
     */
    private static RedisUtil redisUtil;

    /**
     * 使用静态变量注入CommunicationService
     */
    private static CommunicationService communicationService;

    /**
     * 设置RedisUtil的方法
     */
    @Autowired
    public void setRedisUtil(RedisUtil redisUtil) {
        ZgWebSocketServer.redisUtil = redisUtil;
    }

    /**
     * 设置CommunicationService的方法
     */
    @Autowired
    public void setCommunicationService(CommunicationService communicationService) {
        ZgWebSocketServer.communicationService = communicationService;
    }

    /**
     * 无参构造函数，WebSocket规范要求
     */
    public ZgWebSocketServer() {
        super();
    }

    /**
     * 客户端ID
     */
    private String clientId;

    /**
     * 用户信息
     */
    private User user;

    /**
     * 用户账号
     */
    private String userName;

    /**
     * 登录UUID
     */
    private String uuid;

    /**
     * 建立WebSocket连接
     *
     * @param session  会话
     * @param clientId 客户端ID
     */
    @OnOpen
    @Override
    public void onOpen(Session session, @PathParam(value = "clientId") String clientId) {
        try {
            // 验证并设置用户信息
            if (!validateAndSetUserInfo(session, clientId)) {
                return;
            }

            // 检查连接状态并处理
            if (!handleConnectionStatus(session, clientId)) {
                return;
            }
            // 连接成功，调用父类方法完成连接建立
            super.onOpen(session, clientId);
            log.info("ZG-WebSocket连接已建立 - 用户: {}, 客户端ID: {}, 会话ID: {}",
                    user.getUsername(), clientId, session.getId());
        } catch (Exception e) {
            log.error("建立WebSocket连接时发生异常", e);
            closeSession(session, CloseReason.CloseCodes.UNEXPECTED_CONDITION, "服务器内部错误");
        }
    }

    /**
     * 验证并设置用户信息
     *
     * @param session  会话
     * @param clientId 客户端ID
     * @return 验证是否通过
     */
    private boolean validateAndSetUserInfo(Session session, String clientId) {
        // 获取token参数
        String token = getTokenFromSession(session);
        if (!StringUtils.hasText(token)) {
            log.warn("WebSocket连接被拒绝：未提供token - 客户端ID: {}", clientId);
            closeSession(session, CloseReason.CloseCodes.VIOLATED_POLICY, "需要登录");
            return false;
        }

        // 验证token
        User user = TokenUtil.validateToken(token);
        if (user == null) {
            log.warn("WebSocket连接被拒绝：无效的token - 客户端ID: {}", clientId);
            closeSession(session, CloseReason.CloseCodes.VIOLATED_POLICY, "无效的登录信息");
            return false;
        }

        // 设置会话上下文
        this.user = user;
        this.userName = user.getUsername();
        this.clientId = clientId;

        return true;
    }

    /**
     * 处理连接状态
     *
     * @param session  会话
     * @param clientId 客户端ID
     * @return 处理是否成功
     */
    private boolean handleConnectionStatus(Session session, String clientId) {
        // 解析客户端ID格式
        String clientType = null;
        String clientUuid = null;
        String clientUserName = null;

        // PC端连接处理
        if (clientId.startsWith(AssistantContants.CLIENT_TYPE_PC)) {
            clientType = AssistantContants.CLIENT_TYPE_PC;
            String[] parts = parseClientId(clientId, clientType);
            clientUuid = parts[0];
            clientUserName = parts[1];

            // 创建Redis存储键：uuid+username
            String channelKey = AssistantContants.REDIS_KEY_WEBSOCKET_CHANNEL + clientUuid + "-" + userName;
            Object channelValue = redisUtil.get(channelKey);

            // 如果值为true，说明已存在PC端和APP端都建立好了连接
            if (channelValue != null && AssistantContants.CONN_STATUS_TRUE.equals(channelValue.toString())) {
                log.warn("WebSocket连接被拒绝：用户[{}]已存在PC端和APP端建立好了连接", userName);
                closeSession(session, CloseReason.CloseCodes.VIOLATED_POLICY, "已存在连接");
                return false;
            }

            // 如果已经有APP端连接，则设置为true
            if (channelValue != null && AssistantContants.CONN_STATUS_APP.equals(channelValue.toString())) {
                redisUtil.set(channelKey, AssistantContants.CONN_STATUS_TRUE);
                log.info("PC端连接成功，APP端已连接，更新Redis状态为true - 用户ID: {}, UUID: {}", userName, clientUuid);
            } else {
                // 否则标记为PC端已连接
                redisUtil.set(channelKey, AssistantContants.CONN_STATUS_PC);
                log.info("PC端连接成功，等待APP端连接 - 用户ID: {}, UUID: {}", userName, clientUuid);
            }
            return true;
        }

        // APP端连接处理
        if (clientId.startsWith(AssistantContants.CLIENT_TYPE_APP)) {
            clientType = AssistantContants.CLIENT_TYPE_APP;
            String[] parts = parseClientId(clientId, clientType);
            clientUuid = parts[0];
            clientUserName = parts[1];

            // 创建Redis存储键：uuid+username
            String channelKey = AssistantContants.REDIS_KEY_WEBSOCKET_CHANNEL + clientUuid + "-" + userName;
            Object channelValue = redisUtil.get(channelKey);

            log.info("APP端连接 - 用户: {}, UUID: {}", userName, clientUuid);
            log.info("APP端连接 - clientUserName: {}", clientUserName);

            if (userName.equals(clientUserName)) {
                // 如果已经有PC端连接，则设置为true
                if (channelValue != null && AssistantContants.CONN_STATUS_PC.equals(channelValue.toString())) {
                    redisUtil.set(channelKey, AssistantContants.CONN_STATUS_TRUE);
                    log.info("APP端连接成功，PC端已连接，更新Redis状态为true - 用户ID: {}, UUID: {}", userName, clientUuid);
                } else {
                    // 否则标记为APP端已连接
                    redisUtil.set(channelKey, AssistantContants.CONN_STATUS_APP);
                    log.info("APP端连接成功，等待PC端连接 - 用户ID: {}, UUID: {}", userName, clientUuid);
                }
                this.uuid = clientUuid;
                return true;
            } else {
                log.warn("APP端连接被拒绝：PC端和移动端登录的不是同一用户 - 用户ID: {}", userName);
                closeSession(session, CloseReason.CloseCodes.VIOLATED_POLICY, "无效的扫码连接");
                return false;
            }
        }

        return false;
    }

    /**
     * 解析客户端ID
     * 格式：[clientType]-[uuid]-[username]
     * 注意：username可能包含'-'字符
     *
     * @param clientId 客户端ID
     * @param clientType 客户端类型前缀
     * @return 包含uuid和username的数组
     */
    private String[] parseClientId(String clientId, String clientType) {
        // 移除客户端类型前缀
        String withoutPrefix = clientId.substring(clientType.length());

        // UUID固定长度为32个字符，加上开头的'-'共33个字符
        if (withoutPrefix.length() <= 33) {
            log.error("客户端ID格式错误: {}", clientId);
            return new String[]{"", ""};
        }

        // 获取UUID
        String uuid = withoutPrefix.substring(0, 32);
        this.uuid = uuid;

        // 获取用户名 (UUID后面的所有内容，包括'-'字符)
        String username = withoutPrefix.substring(33); // +1 是为了跳过UUID后的'-'

        return new String[]{uuid, username};
    }

    /**
     * 接收客户端消息
     *
     * @param message 接收的消息
     */
    @OnMessage
    @Override
    public void onMessage(String message) throws IOException {
        // 调用父类处理心跳等基本消息
        super.onMessage(message);

        try {
            // 跳过心跳消息
            if (message != null && "heart".equals(message)) {
                return;
            }

            // 发送消息并保存日志
            boolean saveResult = communicationService.sendWebSocketMessage(message, uuid, userName);

            if (saveResult) {
                log.info("消息记录已成功保存 - 用户ID: {}", userName);
            } else {
                log.warn("消息记录保存失败 - 用户ID: {}", userName);
            }

            // 向发送方返回成功消息
            sendMsg(clientId, "消息已成功发送到对端");
        } catch (Exception e) {
            log.error("处理WebSocket消息时发生异常", e);
        }
    }

    /**
     * 连接关闭
     */
    @OnClose
    @Override
    public void onClose() {
        log.info("ZG-WebSocket连接已关闭 - 客户端ID: {}, 用户: {}",
                clientId, user != null ? user.getUsername() : "未登录");

        // 如果是PC端断开连接，同时断开APP端连接
        if (clientId != null && clientId.startsWith(AssistantContants.CLIENT_TYPE_PC) && userName != null) {
            // 获取所有以 app- 开头，后接相同 uuid 和 username 的客户端连接
            String appClientIdPrefix = AssistantContants.CLIENT_TYPE_APP + uuid + "-" + userName;
            disconnectAppClients(appClientIdPrefix);
        }

        // 删除redis中的记录
        cleanupRedisChannel();

        super.onClose();
    }

    /**
     * 断开所有匹配的APP客户端连接
     * @param appClientIdPrefix APP客户端ID前缀
     */
    private void disconnectAppClients(String appClientIdPrefix) {
        // 遍历所有连接的客户端
        for (String connectedClientId : WebSocketServer.clientPool.keySet()) {
            // 检查是否为对应的APP客户端
            if (connectedClientId.startsWith(appClientIdPrefix)) {
                List<Session> sessions = WebSocketServer.clientPool.get(connectedClientId);
                if (sessions != null) {
                    for (Session session : sessions) {
                        if (session != null && session.isOpen()) {
                            try {
                                // 向APP端发送断开通知
                                WebSocketServer.sendMsg(connectedClientId, "PC端已断开连接，APP端将自动断开");

                                session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "PC端已断开连接"));
                                log.info("已关闭APP端连接 - 用户: {}, 客户端ID: {}", userName, connectedClientId);
                            } catch (IOException e) {
                                log.error("关闭APP端连接失败 - 用户: {}, 客户端ID: {}", userName, connectedClientId, e);
                            }
                        }
                    }
                    // 从连接池中移除
                    WebSocketServer.clientPool.remove(connectedClientId);
                }
            }
        }
    }

    /**
     * 清理Redis中的通道记录
     */
    private void cleanupRedisChannel() {
        if (userName != null && uuid != null) {
            try {
                String channelKey = AssistantContants.REDIS_KEY_WEBSOCKET_CHANNEL + uuid + "-" + userName;
                Object channelValue = redisUtil.get(channelKey);

                if (channelValue == null) {
                    log.info("Redis中无需清理的通道记录 - 用户ID: {}, UUID: {}, 客户端ID: {}", userName, uuid, clientId);
                    return;
                }

                // PC端断开连接
                if (clientId.startsWith(AssistantContants.CLIENT_TYPE_PC)) {
                    // PC端断开时，直接删除记录，因为APP端也会被断开
                    redisUtil.del(channelKey);
                    log.info("PC端断开连接，删除WebSocket通道Redis记录 - 用户ID: {}, UUID: {}", userName, uuid);
                }
                // APP端断开连接
                else if (clientId.startsWith(AssistantContants.CLIENT_TYPE_APP)) {
                    if (AssistantContants.CONN_STATUS_TRUE.equals(channelValue.toString())) {
                        // APP断开但PC仍连接，将状态改为PC连接
                        redisUtil.set(channelKey, AssistantContants.CONN_STATUS_PC);
                        log.info("APP端断开连接，PC端仍连接，更新状态为PC - 用户ID: {}, UUID: {}", userName, uuid);
                    } else {
                        // 只有APP连接，直接删除记录
                        redisUtil.del(channelKey);
                        log.info("删除WebSocket通道Redis记录 - 用户ID: {}, UUID: {}", userName, uuid);
                    }
                } else {
                    // 其他情况，安全删除
                    redisUtil.del(channelKey);
                    log.info("删除未知类型的WebSocket通道Redis记录 - 用户ID: {}, UUID: {}", userName, uuid);
                }
            } catch (Exception e) {
                log.error("处理Redis通道记录失败 - 用户ID: {}, UUID: {}", userName, uuid, e);
            }
        }
    }

    /**
     * 发生错误
     *
     * @param throwable 异常
     */
    @OnError
    @Override
    public void onError(Throwable throwable) {
        log.error("ZG-WebSocket连接发生错误 - 客户端ID: {}, 用户: {}, 错误: {}",
                clientId, user != null ? user.getUsername() : "未登录", throwable.getMessage());

        // 清理资源
        cleanupRedisChannel();

        super.onError(throwable);
    }

    /**
     * 从Session获取token
     *
     * @param session WebSocket会话
     * @return token字符串，如果不存在则返回null
     */
    private String getTokenFromSession(Session session) {
        Map<String, List<String>> params = session.getRequestParameterMap();
        if (params.containsKey(C.TOKEN_KEY) && !params.get(C.TOKEN_KEY).isEmpty()) {
            return params.get(C.TOKEN_KEY).get(0);
        }
        return null;
    }

    /**
     * 安全关闭WebSocket会话
     *
     * @param session WebSocket会话
     * @param code 关闭代码
     * @param reason 关闭原因
     */
    private void closeSession(Session session, CloseReason.CloseCodes code, String reason) {
        if (session != null && session.isOpen()) {
            try {
                session.close(new CloseReason(code, reason));
            } catch (IOException e) {
                log.error("关闭WebSocket连接失败", e);
            }
        }
    }
}
